/* Copyright (c) 2011 Alexandre Défossez
 * This file is part of HINAPT.
 *
 * HINAPT is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * HINAPT is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with HINAPT.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "GLThread.hpp"
#include "ElasticModel.hpp"

using namespace sf;


void GLThread::doCapture(){
  static int i=0;
  Image capt = AppGL.Capture();
  string name = "capture-"+toString(i++)+".png";
  capt.SaveToFile(name);
}


GLThread::GLThread(RenderWindow &AppGL, Mutex &AppLock,
             ThQueue<std::pair<Point3d, double> > &headPosQueue, GLuint targetTexture,
             HinaptState &hinaptState):AppGL(AppGL), AppLock(AppLock),
             headPosQueue(headPosQueue), targetTexture(targetTexture), hinaptState(hinaptState)
{
}

void GLThread::Run(){
    Clock frClock; frClock.Reset(); // utilisée pour le framerate
    Point3d currentPos(0, 0, 60); // position actuelle de la tête
    std::pair<Point3d, double> newPos; // nouvelle position de la tête
    Point3d usedPos = currentPos; // position utilisée pour le rendu 3D
    double isLost = true; // vrai si la tête est perdue
    bool immersive3D = true; // si vrai, on active l'effet d'illusion 3D
    ElasticModel elasticModel; // modèle élastique pour lisser le mouvement

    AppLock.Lock();
    AppGL.SetActive(true);
    while (AppGL.IsOpened()){
      Event Event;
      while (AppGL.GetEvent(Event)){// traitement des entrées
          switch(Event.Type){
            case sf::Event::Closed:
              AppGL.Close();
              break;
            case sf::Event::KeyPressed:
              switch (Event.Key.Code){
                case sf::Key::Escape:
                  AppGL.Close();
                  break;
                case sf::Key::F1:
                  doCapture();
                  break;
                case sf::Key::F2:
                  immersive3D = !immersive3D;
                  break;
                default:
                  break;
              }
              break;
            default:
              break;

          }
      }
      double lastTime = frClock.GetElapsedTime();
      AppGL.SetActive(false);
      AppLock.Unlock();
      /* On dort si on est en avance pour dessiner, inutile de faire
      plus de 50 images par seconde */
      Sleep(std::max(0., 1./50. - lastTime));

      AppLock.Lock();
      if (hinaptState.hasBeenLost){
        isLost = true;
        hinaptState.hasBeenLost = false;;
      }
      AppLock.Unlock();

      bool gotAPos = false;
      // On récupère les positions calculées
      while (headPosQueue.pop(newPos)){
        Vec3d Vi(0,0,0);
        if (isLost){
          currentPos = newPos.first;
        } else {
          Vi = elasticModel.getSpeed();
          currentPos = elasticModel.getPosition();
        }
        isLost = false;
        gotAPos = true;

        Point3d dest = newPos.first;
        if (distance(dest, currentPos)>0.){
          double scale = 1.; // éventuellement pour amplifier le mouvemement
          dest.x = (dest.x - currentPos.x)*scale + currentPos.x;
          dest.y = (dest.y - currentPos.y)*scale + currentPos.y;
          dest.z = (dest.z - currentPos.z)*scale + currentPos.z;
        } else
          dest =currentPos;
        elasticModel.init(currentPos, dest, Vi);
      }

      if ((!gotAPos) && (!isLost)){
        /* Si on a pas eu de nouvelle position, on interpôle avec le modèle
        élastique */
        currentPos = elasticModel.getPosition();

      }

      AppLock.Lock();


      AppGL.Clear();
      if (immersive3D)
        usedPos = currentPos;
      draw(usedPos);

      frClock.Reset();
      AppGL.Display();
    }
  AppLock.Unlock();
}



void GLThread::drawGrid(){
  double zmax = 100.;
  double zmin = -10;
  double xmin = - zmax/sqrt(2);
  double xmax = + zmax/sqrt(2);
  double ymin = - zmax/sqrt(2);
  double ymax = + zmax/sqrt(2);
  double step = 5.;

  double nb = (zmax-zmin)/step;


  glBegin(GL_LINES);
  for(int i=0;i<=nb;i++) {
      glColor3f(.25,.25,.25);
      glVertex3f(xmin,ymin,zmin+step*i);
      glVertex3f(xmin,ymax,zmin+step*i);
      glColor3f(.25,.25,.25);
      glVertex3f(xmax, ymin,zmin+step*i);
      glVertex3f(xmax,ymax,zmin+step*i);

      glColor3f(.25,.25,.25);
      glVertex3f(xmin,ymin,zmin+step*i);
      glVertex3f(xmax,ymin,zmin+step*i);
      glColor3f(.25,.25,.25);
      glVertex3f(xmin, ymax,zmin+step*i);
      glVertex3f(xmax,ymax,zmin+step*i);

  };
  for(int i=0; i<=(ymax-ymin)/step; i++){
      glColor3f(.25,.25,.25);
      glVertex3f(xmin,ymin+step*i,zmax);
      glVertex3f(xmax,ymin+step*i,zmax);
      glColor3f(.25,.25,.25);
      glVertex3f(xmin + step*i, ymin,zmax);
      glVertex3f(xmin + step*i,ymax,zmax);

      glColor3f(.25,.25,.25);
      glVertex3f(xmax,ymin+step*i,zmin);
      glVertex3f(xmax,ymin+step*i,zmax);
      glColor3f(.25,.25,.25);
      glVertex3f(xmin + step*i, ymax,zmin);
      glVertex3f(xmin + step*i,ymax,zmax);

      glColor3f(.25,.25,.25);
      glVertex3f(xmin,ymin+step*i,zmin);
      glVertex3f(xmin,ymin+step*i,zmax);
      glColor3f(.25,.25,.25);
      glVertex3f(xmin + step*i, ymin,zmin);
      glVertex3f(xmin + step*i,ymin,zmax);
  }
  glEnd();
}


void GLThread::drawTarget(double x, double y, double z, double radius,GLUquadric* params){
  glPushMatrix();
  glTranslated(x,y,z);
  gluDisk(params,0,radius,30,1);

  glDisable(GL_TEXTURE_2D);

  GLUquadric* params2 = gluNewQuadric();
  glColor3f(.99,0.10,0);
  gluCylinder(params2, radius, radius, 0.6, 30, 1);
  gluDeleteQuadric(params2);

  glPopMatrix();

  glBegin(GL_LINES);
      glColor3f(.99,.99,.99);
      glVertex3f(x,y,z+0.1);
      glVertex3f(x,y,100.);
    glEnd();
  glEnable(GL_TEXTURE_2D);

}

void GLThread::draw(Point3d pos){
    glDrawBuffer(GL_BACK);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(-pos.x,pos.y,-pos.z,-pos.x,pos.y,0,0,1,0);

    glEnable(GL_TEXTURE_2D);
    GLUquadric* params = gluNewQuadric();

    glBindTexture(GL_TEXTURE_2D, targetTexture);
    gluQuadricTexture(params,GL_TRUE);
    double rad = 6;

    drawTarget(-8, -16, -10, rad, params);
    drawTarget(20, 20, -5, rad, params);
    drawTarget(15, -10, 0, rad, params);
    drawTarget(-31, 21, 6, rad, params);
    drawTarget(-15, 0, -8, rad, params);
    drawTarget(0, 0, -25, rad/2, params);
    drawTarget(0, 40, 30, rad, params);
    drawTarget(5, -40, -6, rad, params);
    drawTarget(-40, 40, -10, rad, params);

    gluDeleteQuadric(params);

    glDisable(GL_TEXTURE_2D);
    drawGrid();

}
